///////////////////////////
//
// File: bcf_palette_menu.cpp
//
// Author: Brian Fehrman
//
// Purpose: Contains the class implementation for the Palette menu functions which include such functions
//          grayscale, negate, posterize, etc.
//
///////////////////////////

#include "bcf_img_proc.h"

//////////////////////
// Function: Brightness
// Author: Brian Fehrman
//
// Purpose: Takes an image and adjust the brightness based on number
//          given by the user
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Brightness(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    int brightness = 3;

    if( Dialog("Adjust Brightness")
        .Add( brightness, "Brightness", NUM_PIX_VALS / -2, NUM_PIX_VALS / 2).Show() )
    {
        int nrows = image.Height();
        int ncols = image.Width();

        //For each row in the image
        for ( int curr_r = 0; curr_r < nrows; curr_r++ )
        {
            //For each column in the image
            for( int curr_c = 0; curr_c < ncols; curr_c++  )
            {
                //Get the posterized intensity value. Looks like it doesn't do much but
                //remeber this is integer division
                int intensity = image[ curr_r ][ curr_c ] + brightness;

                //Clip intensity value to be within range
                if( intensity < 0 )
                {
                    intensity = 0;
                }
                else if( intensity > NUM_PIX_VALS - 1 )
                {
                    intensity = NUM_PIX_VALS - 1;
                }

                //Set new intensity value for the current pixel
                image[ curr_r ][ curr_c] = intensity;
            }
        }

        //Return true in order to update the image
        return true;
    }
    else
        return false;
}

//////////////////////
// Function: Contrast
// Author: Brian Fehrman
//
// Purpose: Takes an image and adjust the contrast based on end values
//          given by the user
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Contrast(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    int left_end = 0;
    int LUT[ NUM_PIX_VALS ] = { 0 };
    int right_end = NUM_PIX_VALS - 1;
    float mult_factor = 1.0;

    //Get user end points for contrast adjustment
    Dialog dlg("Adjust Contrast");
    dlg.Add( left_end, "Left End", 0, NUM_PIX_VALS - 1);
    dlg.Add( right_end, "Right End", 0, NUM_PIX_VALS - 1);

    if( dlg.Show())
    {
        //Determine the multiplication factor
        mult_factor = ( NUM_PIX_VALS - 1.0) / (right_end - left_end);

        //Create look up table
        for( int curr_idx = 0; curr_idx < NUM_PIX_VALS; curr_idx++ )
        {
            int intensity = ( mult_factor * ( curr_idx - left_end ) + 0.5 );

            //Clip intensity to be in range
            if( intensity < 0 )
            {
                intensity = 0;
            }
            else if( intensity > NUM_PIX_VALS - 1 )
            {
                intensity = NUM_PIX_VALS - 1;
            }

            LUT[ curr_idx ] = intensity;
        }

        int nrows = image.Height();
        int ncols = image.Width();

        //For each row in the image
        for ( int curr_r = 0; curr_r < nrows; curr_r++ )
        {
            //For each column in the image
            for( int curr_c = 0; curr_c < ncols; curr_c++  )
            {
                //Set new intensity value for the current pixel
                image[ curr_r ][ curr_c] = LUT[ image[ curr_r ][ curr_c ] ];
            }
        }

        //Return true in order to update the image
        return true;
    }
    else
        return false;
}

//////////////////////
// Function: Gamma
// Author: Brian Fehrman
//
// Purpose: Takes an image and adjusts the gamma based on number
//          given by the user. Uses the Power-Law tansform:
//
//          s = c * i^gamma
//
//          Where c is chosen to be 1 and i is the current intensity value
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Gamma(Image &image)
{

    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    double gamma = 1.0;

    //Create gamma dialog box and get value
    Dialog dlg("Adjust Gamma");
    dlg.Add( gamma, "Gamma Value");

    if(dlg.Show())
    {
        int nrows = image.Height();
        int ncols = image.Width();

        //For each row in the image
        for ( int curr_r = 0; curr_r < nrows; curr_r++ )
        {
            //For each column in the image
            for( int curr_c = 0; curr_c < ncols; curr_c++  )
            {
                //Get the gamma adjusted values
                int red = 255.0 * pow( image[ curr_r ][ curr_c ].Red() / 255.0, gamma ) + 0.5;
                int green = 255.0 * pow( image[ curr_r ][ curr_c ].Green() / 255.0, gamma ) + 0.5;
                int blue = 255.0 * pow( image[ curr_r ][ curr_c ].Blue() / 255.0, gamma ) + 0.5;

                //Clip red value to be within range
                if( red < 0 )
                {
                    red = 0;
                }
                else if( red > 255 )
                {
                    red = 255;
                }

                //Clip green value to be within range
                if( green < 0 )
                {
                    green = 0;
                }
                else if( green > 255 )
                {
                    green = 255;
                }

                //Clip blue value to be within range
                if( blue < 0 )
                {
                    blue = 0;
                }
                else if( blue > 255 )
                {
                    blue = 255;
                }

                //Set new intensity value for the current pixel
                image[ curr_r ][ curr_c].SetRed(red);
                image[ curr_r ][ curr_c].SetGreen(green);
                image[ curr_r ][ curr_c].SetBlue(blue);
            }
        }

        //Return true in order to update the image
        return true;
    }
    else
        return false;
}

//////////////////////
// Function: Grayscale
// Author: Brian Fehrman
//
// Purpose: Takes an image and converts it to grayscale using the .30*R + .59*G + .11*B ratio
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Grayscale(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    int nrows = image.Height();
    int ncols = image.Width();

    //For each row in the image
    for ( int curr_r = 0; curr_r < nrows; curr_r++ )
    {
        //For each column in the image
        for( int curr_c = 0; curr_c < ncols; curr_c++  )
        {
            //Get values for each channel
            int red = image[ curr_r ][ curr_c ].Red();
            int blue = image[ curr_r ][ curr_c ].Green();
            int green = image[ curr_r ][ curr_c ].Blue();

            //Convert to grayscale using a weighted average, add 0.5 to round properly
            int gray = static_cast< int > ( 0.3 * red + 0.59 * green + 0.11 * blue ) + 0.5;

            //Set all three channels at once
            image[ curr_r ][ curr_c].SetGray( gray );
        }
    }

    //Return true in order to update the image
    return true;
}

//////////////////////
// Function: Log Correction
// Author: Brian Fehrman
//
// Purpose: Takes an image performs a log tranfsform range compression using:
//
//          s = c * log( 1 + i )
//
//          Where c is chosen such that s is between 0 and 255 and i is the current intensity value
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Log(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    int nrows = image.Height();
    int ncols = image.Width();

    //Determine scale value
    float c_scale = 255.0 / log( 256 );

    //For each row in the image
    for ( int curr_r = 0; curr_r < nrows; curr_r++ )
    {
        //For each column in the image
        for( int curr_c = 0; curr_c < ncols; curr_c++  )
        {
            //Get the log adjusted values
            int red = c_scale * log( 1.0 + image[ curr_r ][ curr_c ].Red() );
            int green = c_scale * log( 1.0 + image[ curr_r ][ curr_c ].Green() );
            int blue = c_scale * log( 1.0 + image[ curr_r ][ curr_c ].Blue() );

            //Set new intensity value for the current pixel
            image[ curr_r ][ curr_c].SetRed(red);
            image[ curr_r ][ curr_c].SetGreen(green);
            image[ curr_r ][ curr_c].SetBlue(blue);
        }
    }

    //Return true in order to update the image
    return true;
}

//////////////////////
// Function: Negate
// Author: Brian Fehrman
//
// Purpose: Takes an image and negates the value of each channel
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Negate(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    int nrows = image.Height();
    int ncols = image.Width();

    //For each row in the image
    for ( int curr_r = 0; curr_r < nrows; curr_r++ )
    {
        //For each column in the image
        for( int curr_c = 0; curr_c < ncols; curr_c++  )
        {
            //Get values for each channel and negate them
            int red = 255 - image[ curr_r ][ curr_c ].Red();
            int blue = 255 - image[ curr_r ][ curr_c ].Green();
            int green = 255 - image[ curr_r ][ curr_c ].Blue();

            //Set each channel to new pixel value
            image[ curr_r ][ curr_c].SetRed( red );
            image[ curr_r ][ curr_c].SetGreen( green );
            image[ curr_r ][ curr_c].SetBlue( blue );
        }
    }

    //Return true in order to update the image
    return true;
}

//////////////////////
// Function: Posterize
// Author: Brian Fehrman
//
// Purpose: Takes an image and posterizes the intensity values based on number
//          given by the user
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Posterize(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    int levels = 10;
    Dialog dlg("Posterize Intensity Levels");
    dlg.Add( levels, "Number of Levels");

    if( dlg.Show() )
    {
        //Force levels value to be in correct range
        if( levels < 1)
        {
            levels = 1;
        }
        else if( levels > 255 )
        {
            levels = 255;
        }

        //Get the divider to break up the intensity levels
        int divider = 255.0 / levels + 0.5;
        int nrows = image.Height();
        int ncols = image.Width();

        //I don't know that this is actually needed as the program didn't seem to complain
        //when I made calls to Intensity() without the HSI converison being called first.
        image.ToHSI();

        //For each row in the image
        for ( int curr_r = 0; curr_r < nrows; curr_r++ )
        {
            //For each column in the image
            for( int curr_c = 0; curr_c < ncols; curr_c++  )
            {
                //Get the posterized intensity value. Looks like it doesn't do much but
                //remeber this is integer division
                int intensity = image[ curr_r ][ curr_c ].Intensity() / divider * divider + 0.5;

                //Set new intensity value for the current pixel
                image[ curr_r ][ curr_c].SetIntensity( intensity );
            }
        }

        //Return true in order to update the image
        return true;
    }
    else
        return false;
}

//////////////////////
// Function: Psuedo8
// Author: Brian Fehrman
//
// Purpose: Takes an image and converts it to 8 level psuedo color based
// on intensity values
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Psuedo8(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    int nrows = image.Height();
    int ncols = image.Width();

    //Divide up the intensity values in to 8 categories
    int step_size = 255.0 / 8.0 + 0.5;

    image.ToHSI();

    //For each row in the image
    for ( int curr_r = 0; curr_r < nrows; curr_r++ )
    {
        //For each column in the image
        for( int curr_c = 0; curr_c < ncols; curr_c++  )
        {
            //Get the psuedo color "bucket" that the current intensity value falls in to
            int p_color = image[ curr_r ][ curr_c ].Intensity() / step_size * step_size + 0.5;

            //Set new HSI values for the current pixel
            image[ curr_r ][ curr_c].SetHue( p_color );
            image[ curr_r ][ curr_c].SetSaturation( 255 );
            image[ curr_r ][ curr_c].SetIntensity( 255 );
        }
    }

    //Return true in order to update the image
    return true;
}

//////////////////////
// Function: PsuedoContinuous
// Author: Brian Fehrman
//
// Purpose: Takes an image and converts it to continuous psuedo color based
// on intensity values
///////////////////////
bool BCF_Img_Proc::Menu_Palette_Psuedo_Continuous(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    int nrows = image.Height();
    int ncols = image.Width();

    image.ToHSI();

    //For each row in the image
    for ( int curr_r = 0; curr_r < nrows; curr_r++ )
    {
        //For each column in the image
        for( int curr_c = 0; curr_c < ncols; curr_c++  )
        {
            //Set new HSI values for the current pixel
            image[ curr_r ][ curr_c].SetHue( image[ curr_r ][ curr_c ].Intensity());
            image[ curr_r ][ curr_c].SetSaturation( 255 );
            image[ curr_r ][ curr_c].SetIntensity( 255 );
        }
    }

    //Return true in order to update the image
    return true;
}

//////////////////////
// Function: Random Color
// Author: Brian Fehrman
//
// Purpose: Takes an image and randomly maps its intensity values
//          colors in the Saturation domain.
//////////////////////
bool BCF_Img_Proc::Menu_Palette_Random_Color(Image &image)
{
    //No point in doing anything if we don't have an image with which to work.
    if ( image.IsNull() ) return false;

    srand( time( NULL ) );

    int nrows = image.Height();
    int ncols = image.Width();
    int colors_left = NUM_PIX_VALS - 1;
    int LUT[ NUM_PIX_VALS ];

    std::vector<uint> colors;
    colors.resize( NUM_PIX_VALS );

    //Fill list from which we will randomly grab color values
    for( int curr_idx = 0; curr_idx < NUM_PIX_VALS; curr_idx++)
    {
        colors[ curr_idx ] = curr_idx;
    }

    //Generate look up table based on randomly mapping intensities
    //to color values
    for( int curr_idx = 0; curr_idx < NUM_PIX_VALS; curr_idx++ )
    {
        if( colors_left > 0 )
        {
            int curr_rand_idx = rand() % colors_left;

            LUT[ curr_idx ] = colors[ curr_rand_idx ];

            //Remove the color that was chosen from the list so
            //that it is not chosen again
            colors.erase( colors.begin() + curr_rand_idx );
            colors_left--;
        }
        else
        {
            LUT[ curr_idx ] = colors[ 0 ];
        }
    }

    image.ToHSI();

    //For each row in the image
    for ( int curr_r = 0; curr_r < nrows; curr_r++ )
    {
        //For each column in the image
        for( int curr_c = 0; curr_c < ncols; curr_c++  )
        {
            //Set new HSI values for the current pixel
            image[ curr_r ][ curr_c].SetHue( LUT[ image[ curr_r ][ curr_c ] ]);
            image[ curr_r ][ curr_c].SetSaturation( 255 );
            image[ curr_r ][ curr_c].SetIntensity( 255 );
        }
    }

    return true;
}
